# dingtalk_bot.py

import time
import hashlib
import hmac
import base64
import urllib.parse
import requests
import json


class DingTalkBot:
	def __init__(self, config):
		"""
        初始化类并加载钉钉机器人配置

        :param config: 字典格式的钉钉机器人配置，例如：
            {
                "group_name": "test",
                "webhook": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
                "secret": "SECxxx",
                "at_mobiles": ["18086409233", "18696147347"]
            }
        """
		required_fields = ["group_name", "webhook", "secret"]
		for field in required_fields:
			if field not in config:
				raise ValueError(f"缺少必要字段: {field}")
		
		self.config = config
		self.group_name = config["group_name"]
		self.webhook = config["webhook"]
		self.secret = config["secret"]
		self.at_mobiles = config.get("at_mobiles", [])
	
	def _sign(self):
		"""生成带签名的 URL"""
		timestamp = str(round(time.time() * 1000))
		secret_enc = self.secret.encode('utf-8')
		string_to_sign = f"{timestamp}\n{self.secret}"
		hmac_code = hmac.new(secret_enc, string_to_sign.encode('utf-8'), digestmod=hashlib.sha256).digest()
		sign = urllib.parse.quote_plus(base64.b64encode(hmac_code).decode('utf-8'))
		
		return f"{self.webhook}&timestamp={timestamp}&sign={sign}"
	
	def send_text(self, content, at_mobiles=None):
		"""
        发送文本消息

        :param content: 消息内容
        :param at_mobiles: 要 @ 的手机号列表
        :return: 请求响应结果
        """
		if at_mobiles is None:
			at_mobiles = self.at_mobiles
		
		url = self._sign()
		headers = {'Content-Type': 'application/json', 'Charset': 'UTF-8'}
		data = {
			"msgtype": "text",
			"text": {"content": content},
			"at": {"atMobiles": at_mobiles or [], "isAtAll": False}
		}
		try:
			response = requests.post(url=url, data=json.dumps(data), headers=headers, timeout=10)
			return response.json()
		except Exception as e:
			print(f"发送消息失败：{e}")
			return {"error": str(e)}
	
	def send_markdown_report(self, title, download_link, at_all=False):
		"""
        发送 Markdown 格式的报告消息

        :param title: 报告标题
        :param download_link: 下载链接（文件名）
        :param at_all: 是否 @ 所有人
        :return: 请求响应结果
        """
		full_url = f"{download_link}"  # 构造完整下载地址
		url = self._sign()
		headers = {'Content-Type': 'application/json', 'Charset': 'UTF-8'}
		data = {
			"msgtype": "markdown",
			"markdown": {
				"title": title,
				"text": f"# {time.strftime('%Y-%m-%d %H:%M')} {title}\n>"
				        f"#### [{download_link}]({full_url})"
			},
			"at": {"isAtAll": at_all}
		}
		try:
			response = requests.post(url=url, data=json.dumps(data), headers=headers, timeout=10)
			return response.json()
		except Exception as e:
			print(f"发送消息失败：{e}")
			return {"error": str(e)}
	
	def send_exception_log(self, file_name, at_all=False):
		"""
        发送异常日志通知（Markdown）

        :param file_name: 文件名
        :param at_all: 是否 @ 所有人
        :return: 请求响应结果
        """
		return self.send_markdown_report(
			title="异常日志下载",
			download_link=file_name,
			at_all=at_all
		)


if __name__ == "__main__":
	config = {
		"group_name": "test",
		"webhook": "https://oapi.dingtalk.com/robot/send?access_token=a0b6a8f98bf081c7b2939be837300372bc8d57ef0a8eff1dc36ed3d9ca609aa9",
		"secret": "SEC823e4f637bb6829e17246c79afb918f22e93ab7eb9d9adbebfedea80d32306e3",
		"at_mobiles": ["18696147347"]
	}
	
	bot = DingTalkBot(config)
	
	# 示例1：发送普通文本消息
	bot.send_text("这是一条测试消息")
	#
	# # 示例2：发送日报（Markdown）
	# bot.send_markdown_report("烤机日报", "filtered_log.zip")
	#
	# # 示例3：发送异常日志（Markdown + @所有人）
	# bot.send_exception_log("error_log_20250627.zip", at_all=True)
